import {
  ActivityBlueprint,
  ActivityDefinitionProperty,
  ActivityDescriptor,
  Connection,
  WorkflowBlueprint,
  WorkflowInstance,
} from "./domain";
import {
  ActivityDesignDisplayContext,
  ActivityModel,
  ConnectionModel,
  WorkflowModel,
} from "./view";
import * as collection from "lodash/collection";

export type Map<T> = {
  [key: string]: T;
};

export function mapWorkflowModel(
  workflowBlueprint: WorkflowBlueprint,
  workflowInstance: WorkflowInstance,
  activityDescriptors: Array<ActivityDescriptor>
): WorkflowModel {
  const activities = workflowBlueprint.activities
    .filter((x) => x.parentId == workflowBlueprint.id || !x.parentId)
    .map((x) => mapActivityModel(x, workflowInstance, activityDescriptors));
  const connections = workflowBlueprint.connections
    .filter(
      (c) =>
        activities.findIndex(
          (a) => a.activityId == c.sourceActivityId || a.activityId == c.targetActivityId
        ) > -1
    )
    .map(mapConnectionModel);

  const activityDisplayContexts = componentWillRender(activities, activityDescriptors);

  return {
    activities: activities,
    connections: connections,
    persistenceBehavior: workflowBlueprint.persistenceBehavior,
    activityDescriptors: activityDescriptors,
    workflowInstance: workflowInstance,
    activityDisplayContexts: activityDisplayContexts,
  };
}

export function componentWillRender(activities, activityDescriptors) {
  const activityDisplayContexts: Map<ActivityDesignDisplayContext> = {};
  for (const model of activities) {
    activityDisplayContexts[model.activityId] = getActivityDisplayContext(
      model,
      activityDescriptors
    );
  }

  return activityDisplayContexts;
}

export function mapActivityModel(
  activityBlueprint: ActivityBlueprint,
  workflowInstance: WorkflowInstance,
  activity: Array<ActivityDescriptor>
): ActivityModel {
  const activityDescriptors: Array<ActivityDescriptor> = activity;
  const activityDescriptor = activityDescriptors.find((x) => x.type == activityBlueprint.type);
  const activityData = workflowInstance.activityData[activityBlueprint.id] || {};

  const properties: Array<ActivityDefinitionProperty> = collection.map(
    activityBlueprint.inputProperties.data,
    (value, key) => {
      const propertyDescriptor =
        activityDescriptor.inputProperties.find((x) => x.name == key) ||
        activityDescriptor.outputProperties.find((x) => x.name == key);
      const defaultSyntax = propertyDescriptor?.defaultSyntax || SyntaxNames.Literal;
      const expressions = {};
      const v = activityData[key] || value;
      expressions[defaultSyntax] = v;
      return { name: key, value: v, expressions: expressions, syntax: defaultSyntax };
    }
  );

  return {
    activityId: activityBlueprint.id,
    description: activityBlueprint.description,
    displayName: activityBlueprint.displayName || activityBlueprint.name || activityBlueprint.type,
    name: activityBlueprint.name,
    type: activityBlueprint.type,
    properties: properties,
    outcomes: [...activityDescriptor.outcomes],
    persistWorkflow: activityBlueprint.persistWorkflow,
    saveWorkflowContext: activityBlueprint.saveWorkflowContext,
    loadWorkflowContext: activityBlueprint.loadWorkflowContext,
    propertyStorageProviders: activityBlueprint.propertyStorageProviders,
    x: activityBlueprint.x,
    y: activityBlueprint.y,
  };
}

export function mapConnectionModel(connection: Connection): ConnectionModel {
  return {
    sourceId: connection.sourceActivityId,
    targetId: connection.targetActivityId,
    outcome: connection.outcome,
  };
}

export function getChildActivities(workflowModel: WorkflowModel, parentId?: string) {
  if (parentId == null) {
    const targetIds = new Set(workflowModel.connections.map((x) => x.targetId));
    return workflowModel.activities.filter((x) => !targetIds.has(x.activityId));
  } else {
    const targetIds = new Set(
      workflowModel.connections.filter((x) => x.sourceId === parentId).map((x) => x.targetId)
    );
    return workflowModel.activities.filter((x) => targetIds.has(x.activityId));
  }
}

export const getActivityDisplayContext = (
  activityModel: ActivityModel,
  activityDescriptors: Array<ActivityDescriptor>
): ActivityDesignDisplayContext => {
  let descriptor = activityDescriptors.find((x) => x.type == activityModel.type);
  let descriptorExists = !!descriptor;
  // const oldContextData = (this.oldActivityDisplayContexts && this.oldActivityDisplayContexts[activityModel.activityId]) || {expanded: false};

  if (!descriptorExists) descriptor = createNotFoundActivityDescriptor(activityModel);

  const description = descriptorExists
    ? activityModel.description
    : `(Not Found) ${descriptorExists}`;
  const bodyText = description && description.length > 0 ? description : undefined;
  const bodyDisplay = bodyText ? `<p>${bodyText}</p>` : undefined;
  const color =
    (descriptor.traits &= ActivityTraits.Trigger) == ActivityTraits.Trigger ? "rose" : "sky";
  const displayName = descriptorExists
    ? activityModel.displayName
    : `(Not Found) ${activityModel.displayName}`;

  const displayContext = {
    activityModel: activityModel,
    activityDescriptor: descriptor,
    // activityIcon: <ActivityIcon color={color}/>,
    bodyDisplay: bodyDisplay,
    displayName: displayName,
    outcomes: [...activityModel.outcomes],
    // expanded: oldContextData.expanded
  };

  // await eventBus.emit(EventTypes.ActivityDesignDisplaying, this, displayContext);
  onActivityDesignDisplaying(displayContext);
  return displayContext;
};

const onActivityDesignDisplaying = (context: ActivityDesignDisplayContext) => {
  const propValuesAsOutcomes = context.activityDescriptor.inputProperties.filter(
    (prop) => prop.considerValuesAsOutcomes
  );

  if (propValuesAsOutcomes.length > 0) {
    const props = context.activityModel.properties || [];
    const syntax = SyntaxNames.Json;
    let dynamicOutcomes = [];
    props
      .filter((prop) => propValuesAsOutcomes.find((desc) => desc.name == prop.name) != undefined)
      .forEach((prop) => {
        const expression = prop.expressions[syntax] || [];
        const dynamicPropertyOutcomes = !!expression["$values"]
          ? expression["$values"]
          : Array.isArray(expression)
            ? expression
            : parseJson(expression) || [];
        dynamicOutcomes = [...dynamicOutcomes, ...dynamicPropertyOutcomes];
      });
    context.outcomes = [...dynamicOutcomes, ...context.outcomes];
  }
};

const createNotFoundActivityDescriptor = (activityModel: ActivityModel): ActivityDescriptor => {
  return {
    outcomes: ["Done"],
    inputProperties: [],
    type: `(Not Found) ${activityModel.type}`,
    outputProperties: [],
    displayName: `(Not Found) ${activityModel.displayName || activityModel.name || activityModel.type}`,
    traits: ActivityTraits.Action,
    description: `(Not Found) ${activityModel.description}`,
    category: "Not Found",
    browsable: false,
    customAttributes: {},
  };
};

export function parseJson(json: string): any {
  if (!json) return null;

  try {
    return JSON.parse(json);
  } catch (e) {
    console.warn(`Error parsing JSON: ${e}`);
  }
  return undefined;
}
